热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

尺寸|后记_OpenPPLPPQ量化:原理与实践

篇首语:本文由编程笔记#小编为大家整理,主要介绍了OpenPPLPPQ量化:原理与实践相关的知识,希望对你有一定的参考价值。目录

篇首语:本文由编程笔记#小编为大家整理,主要介绍了OpenPPL PPQ量化:原理与实践相关的知识,希望对你有一定的参考价值。


目录

量化原理

为什么需要量化?

量化粒度

框架综述

算子划分

量化中的图融合操作

量化实践:以pytorch mobilenet v2 模型为例

源码阅读 

torch模型和onnx量化过程中的区别

后记





量化原理


为什么需要量化?

1、减少内存带宽和存储空间

深度学习模型主要是记录每个 layer(比如卷积层/全连接层) 的 weights 和 bias, FP32 模型中,每个 weight 数值原本需要 32-bit 的存储空间,量化之后只需要 8-bit 即可。因此,模型的大小将直接降为将近 1/4。

不仅模型大小明显降低, activation 采用 8-bit 之后也将明显减少对内存的使用,这也意味着低精度推理过程将明显减少内存的访问带宽需求,提高高速缓存命中率,尤其对于像 batch-norm, relu,elmentwise-sum 这种内存约束(memory bound)的 element-wise 算子来说,效果更为明显。

2、提高系统吞吐量(throughput),降低系统延时(latency)

直观理解,试想对于一个 专用寄存器宽度为 512 位的 SIMD 指令,当数据类型为 FP32 而言一条指令能一次处理 16 个数值,但是当我们采用 8-bit 表示数据时,一条指令一次可以处理 64 个数值。因此,在这种情况下,可以让芯片的理论计算峰值增加 4 倍。在CPU上,英特尔至强可扩展处理器的 AVX-512 和 VNNI 高级矢量指令支持低精度和高精度的累加操作。


量化粒度

量化粒度是指共享量化参数的大小,例如 每个 Tensor 共享一组量化参数,那么量化的粒度为 per-tensor。量化的粒度越小,模型的精度越好,但计算成本越高。


  • per-tensor:整个神经网络层用一组量化参数(scale, zero-point)

  • per-channel:一层神经网络每个通道用一组量化参数(scale, zero-point)。那么就是per-channel需要存更多的量化参数,对的计算速度也有一点影响。在深度学习中,张量的每一个通道通常代表一类特征,因此可能会出现不同的通道之间数据分布较大的情况。对于通道之间差异较大的情况仍然使用张量级的量化方式可能对精度产生一定的影响,因此通道级量化就显得格外重要。

为了获得最大的性能,考虑到整数矩阵乘法,量化的粒度应该是:


  • 对于激活的量化,由于性能原因,推荐per-tensor

  • 对于权重的量化,per-channel和per-tensor都行

可以想象到per-channel量化很明显细粒度更高,所以一般来说效果会更好,但当前主流的量化仍然是权重和激活都采用per-tensor量化。


框架综述


整个框架可以分成三部分:

PPQ Paser 模块可读取 onnx 或 caffe 模型,并解析成内部格式。解析完成后,Scheduler 模块对模型进行切分与调度,粗颗粒度地划分量化与非量化算子。

Quantizer 模块是 PPQ 量化执行的中枢,为模型算子分配特定的部署平台,并初始化量化设置,调用各种优化 Pass,完成量化联合定点、图融合及量化优化。

Executor 模块依据模型拓扑关系,调用底层算子实现,执行前向推理。模型量化完成后,调用 Exporter 模块,导出模型和量化参数。


算子划分

PPQ 使用 graph dispatcher 将图中所有算子划分为三类:


  • 不可量化区:这区域的算子与 shape或者 index 有关,一旦量化将导致图的计算发生错误,因此不可量化,同时默认被调度到 Host 端以浮点精度执行。

  • 可量化区:这区域的算子被认为是可以量化的,它们是 input, conv, gemm 的延伸算子,PPQ 使用数值追踪技术标记这些算子,这些算子处理的运算一定是 input, conv, gemm 的计算结果。它们被调度到设备端以 int8 精度执行。

  • 争议区:这区域的算子同时接收来自不可量化区以及可量化区的输入,所有争议区的算子延伸也是争议算子,量化这些算子是有风险的,PPQ 不能保证量化产生的影响。该区算子被调度到设备端以浮点精度执行。

为了找出这些区域,PPQ 使用图搜索引擎进行区域划分,其基本思想是通过枚举所有算子的计算情况,确定输入的来源是否与 shape 或 index 相关。你可以通过 ppq.scheduler 中的代码看到它们的具体实现。

在 PPQ 中,我们实现了三种不同的调度逻辑,不同的调度逻辑将产生不同的区域划分:


  • 激进式调度:该调度方法将所有争议区算子视作可量化的。

  • 保守式调度:该调度方法将所有争议区算子视作不可量化的(它们依然将被调度到设备端)。

  • pplnn:该调度方法只量化卷积层与其相关算子。


量化中的图融合操作

硬件精度未对齐的主要原因在于 —— 推理库后端会对模型做大量的联合定点和图融合优化,我们写入的量化参数已被后端融合或修改,量化模拟与后端推理并不一致,导致优化算法大打折扣。

PPQ 使用 Tensor Quantization Config 类来描述算子数值量化的细节,其绑定在算子之上。


Executor 模块执行每一个算子时,并不会在模型中插入量化节点,而是通过一种类似于 hook 的形式,直接将量化操作添加到算子的执行逻辑中。模型算子输入/输出变量是否量化,由算子输入/输出的 Tensor Quantization Config 的 state 属性决定。


量化实践:以pytorch mobilenet v2 模型为例

首先按照官方教程安装ppq:ppq/quantize_torch_model.py at master · openppl-public/ppq · GitHub

我是使用 Install PPQ from source 方法安装的,直接安装会报错,可能是库之间相互依赖的问题,把requirements.txt文件中的onnx >= 1.9.0改成onnx == 1.9.0即可。

官方提供了一份完整的例子地址:ppq/quantize_torch_model.py at master · openppl-public/ppq · GitHub

打开文件openppl/ppq/ppq/samples运行脚本python quantize_torch_model.py,注意运行前新建一个文件夹Output存放量化后的模型。


源码阅读 

跑通了这个例子我们再来阅读一下源代码。

因为是静态离线量化,所以需要少量的校准数据,这里用随机生成的方法生成校准数据:

def load_calibration_dataset() -> Iterable:
return [torch.rand(size=INPUT_SHAPE) for _ in range(32)]

加载pytorch内置的mobilenet V2模型,如果本地cache没有找到的话,会自动下载模型的配置和权重:

model = torchvision.models.mobilenet.mobilenet_v2(pretrained=True)
model = model.to(DEVICE)

PPL需要创建一个 QuantizationSetting 对象用来管理量化过程,这个是由QuantizationSettingFactory实现的:

# create a setting for quantizing your network with PPL CUDA.
quant_setting = QuantizationSettingFactory.pplcuda_setting()
quant_setting.equalization = True # use layerwise equalization algorithm.
quant_setting.dispatcher = 'conservative' # dispatch this network in conservertive way.

这里设置了三项:


  • 用cuda设置
  • 采用分层均衡算法,这个貌似是这篇论文的,还没细看:https://hailo.ai/wp-content/uploads/2021/03/Exploring-Neural-Networks-Quantizationvia-Layer-Wise-Quantization-Analysis.pdf
  • 以保守的方式调度这个网络,将所有争议区算子视作不可量化的

ppq针对torch模型都封装起来了,只需要调用quantize_torch_model()即可。如果是onnx模型,需要手动自建图调度,最后一样都要使用export_ppq_graph()导出计算图。

# quantize your model.
quantized = quantize_torch_model(
model=model, calib_dataloader=calibration_dataloader,
calib_steps=32, input_shape=[BATCHSIZE] + INPUT_SHAPE,
setting=quant_setting, collate_fn=collate_fn, platform=PLATFORM,
onnx_export_file='Output/onnx.model', device=DEVICE, verbose=0)
# Quantization Result is a PPQ BaseGraph instance.
assert isinstance(quantized, BaseGraph)
# export quantized graph.
export_ppq_graph(graph=quantized, platform=PLATFORM,
graph_save_to='Output/quantized(onnx).onnx',
config_save_to='Output/quantized(onnx).json')

torch模型和onnx量化过程中的区别

onnx模型会直接调用quantize_onnx_model(),torch模型会调用quantize_onnx_model(),这个函数会先执行torch转onnx操作,然后再调用quantize_onnx_model():

@ empty_ppq_cache
def quantize_torch_model(
model: torch.nn.Module,
calib_dataloader: DataLoader,
calib_steps: int,
input_shape: List[int],
platform: TargetPlatform,
input_dtype: torch.dtype = torch.float,
setting: QuantizationSetting = None,
collate_fn: Callable = None,
inputs: List[Any] = None,
do_quantize: bool = True,
onnx_export_file: str = 'onnx.model',
device: str = 'cuda',
verbose: int = 0,
) -> BaseGraph:
"""量化一个 Pytorch 原生的模型 输入一个 torch.nn.Module 返回一个量化后的 PPQ.IR.BaseGraph.
quantize a pytorch model, input pytorch model and return quantized ppq IR graph
Args:
model (torch.nn.Module): 被量化的 torch 模型(torch.nn.Module) the pytorch model
calib_dataloader (DataLoader): 校准数据集 calibration dataloader
calib_steps (int): 校准步数 calibration steps
collate_fn (Callable): 校准数据的预处理函数 batch collate func for preprocessing
input_shape (List[int]): 模型输入尺寸,用于执行 jit.trace,对于动态尺寸的模型,输入一个模型可接受的尺寸即可。
如果模型存在多个输入,则需要使用 inputs 变量进行传参,此项设置为 None
a list of ints indicating size of input, for multiple inputs, please use
keyword arg inputs for direct parameter passing and this should be set to None
input_dtype (torch.dtype): 模型输入数据类型,如果模型存在多个输入,则需要使用 inputs 变量进行传参,此项设置为 None
the torch datatype of input, for multiple inputs, please use keyword arg inputs
for direct parameter passing and this should be set to None
setting (OptimSetting): 量化配置信息,用于配置量化的各项参数,设置为 None 时加载默认参数。
Quantization setting, default setting will be used when set None
inputs (List[Any], optional): 对于存在多个输入的模型,在Inputs中直接指定一个输入List,从而完成模型的tracing。
for multiple inputs, please give the specified inputs directly in the form of
a list of arrays
do_quantize (Bool, optional): 是否执行量化 whether to quantize the model, defaults to True, defaults to True.
platform (TargetPlatform, optional): 量化的目标平台 target backend platform, defaults to TargetPlatform.DSP_INT8.
device (str, optional): 量化过程的执行设备 execution device, defaults to 'cuda'.
verbose (int, optional): 是否打印详细信息 whether to print details, defaults to 0.
Raises:
ValueError: 给定平台不可量化 the given platform doesn't support quantization
KeyError: 给定平台不被支持 the given platform is not supported yet
Returns:
BaseGraph: 量化后的IR,包含了后端量化所需的全部信息
The quantized IR, containing all information needed for backend execution
"""
# dump pytorch model to onnx
dump_torch_to_onnx(model=model, onnx_export_file=onnx_export_file,
input_shape=input_shape, input_dtype=input_dtype,
inputs=inputs, device=device)
return quantize_onnx_model(onnx_import_file=onnx_export_file,
calib_dataloader=calib_dataloader, calib_steps=calib_steps, collate_fn=collate_fn,
input_shape=input_shape, input_dtype=input_dtype, inputs=inputs, setting=setting,
platform=platform, device=device, verbose=verbose, do_quantize=do_quantize)

返回的都是一个量化IR(中间表示),根据这个中间表示再去保存我们所需要的信息。


后记

openppl的中文文档和教程非常完善,堪比paddle,适合基于此学习模型量化。本篇博客是第一篇,大致了解了ppq的设计思想、框架结构,并通过一个简单的例子实践感受。后续的博客会继续探索openppl模型量化!


推荐阅读
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • 深度学习中的Vision Transformer (ViT)详解
    本文详细介绍了深度学习中的Vision Transformer (ViT)方法。首先介绍了相关工作和ViT的基本原理,包括图像块嵌入、可学习的嵌入、位置嵌入和Transformer编码器等。接着讨论了ViT的张量维度变化、归纳偏置与混合架构、微调及更高分辨率等方面。最后给出了实验结果和相关代码的链接。本文的研究表明,对于CV任务,直接应用纯Transformer架构于图像块序列是可行的,无需依赖于卷积网络。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 本文介绍了在Python张量流中使用make_merged_spec()方法合并设备规格对象的方法和语法,以及参数和返回值的说明,并提供了一个示例代码。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
author-avatar
如果你在的时候的世界_266
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有